再读斋

Android 5.x之RecyclerView

RecyclerView是Android官方推出的旨在取代ListView、GridView的控件,可以通过导入support-V7进行使用。据官方介绍,该控件用于在有限的窗口中展示大量数据集,同样能实现此效果的有ListView、GridView。

那么有了ListView、GridView之后为什么还需要RecyclerView这样的控件呢?整体上看RecyclerView架构,它提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供不同的LayoutManager、ItemDecoration、ItemAnimator来实现各种各样的效果。

  • 控制其显示的布局方式,请设置布局管理器LayoutManager
  • 控制Item间的间隔,请实现ItemDecoration
  • 控制Item间增删的动画,请实现ItemAnimator
  • 控制其点击、长按事件,不好意思请自己写

下面,我们开始进入RecyclerView的体验之旅

build.gradle

要想使用RecyclerView,首先我们要导入support-V7包,Android Studio中需要的build.gradle文件中加入下面代码自动导入V7包。

1
2
3
4
5
6
7
8
9
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1' //v7
compile 'com.android.support:design:23.1.1'
compile 'com.android.support:cardview-v7:23.1.1'
compile 'com.android.support:recyclerview-v7:23.1.1'
compile 'com.android.support:palette-v7:23.1.1'
}

RecyclerView实现ListView效果

  1. 实例化RecyclerView
  2. 设置RecyclerView的布局(有ListView、GridView和瀑布流三种效果,水平和垂直两个方向)
  3. 设置RecyclerView的Item分割线
  4. 设置RecyclerView的Item增删时的动画
  5. 实例化一个RecyclerViewAdapter
  6. 设置RecyclerView的Adapter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
//设置布局管理器
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); //垂直方向的list
//mRecyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false)); //水平方向的list
//增加Item分割线,必须在setAdapter方法之前设置
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
//设置增加和删除条目时的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = new RecyclerViewAdapter(this,mDatas);
mRecyclerView.setAdapter(mAdapter);

主界面布局

RecyclerView的类名全称为android.support.v7.widget.RecyclerView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.example.michael.recyclerviewdemo.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>

分割线DividerItemDecoration

DividerItemDecoration是实现的是RecyclerView作为ListView效果时的分割线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.example.michael.recyclerviewdemo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* 横向和纵向的分割线
* Created by Michael on 2016/2/25.
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}

实现Adapter

RecyclerViewAdapter用于RecyclerView作为ListView和GridView效果时Adapter,通过其构造函数传入Context和需要绑定的数据集合。一个基本的Adapter里面有需要实现如下功能:

  • 创建RecyclerView.ViewHolder的子类
  • 视图与数据绑定
  • 点击事件回调接口
  • Item的增加和删除更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.example.michael.recyclerviewdemo;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
/**
* Created by Michael on 2016/2/25.
*/
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder> {
private Context mContext;
private List<String> mDatas;
private OnItemClickListener mListener;
/**
* 点击事件回调接口
*/
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.mListener = listener;
}
public RecyclerViewAdapter(Context context, List<String> datas) {
this.mContext = context;
mDatas = datas;
}
//实例化ViewHolder
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_recyclerview, parent, false);
return new MyViewHolder(view);
}
//绑定数据
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
System.out.println(position);
holder.mClickBtn.setText(mDatas.get(position));
if (mListener != null) {
holder.mClickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取实际的位置
int pos = holder.getLayoutPosition();
mListener.onItemClick(holder.mClickBtn,pos);
}
});
holder.mClickBtn.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();
mListener.onItemLongClick(holder.mClickBtn,pos);
return true; //屏蔽单击 false点击事件仍会响应
}
});
}
}
@Override
public int getItemCount() {
return mDatas.size();
}
/**
* 在当前Item之前添加Item
*/
public void addPreItem(String data,int position) {
mDatas.add(position,data);
notifyItemInserted(position);
}
/**
* 在当前Item之后添加Item
*/
public void addNextItem(String data,int position) {
mDatas.add(position+1,data);
notifyItemInserted(position+1);
}
/**
* 删除Item
* @param position
*/
public void removeItem(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView mClickBtn;
public MyViewHolder(View itemView) {
super(itemView);
mClickBtn = (TextView) itemView.findViewById(R.id.btn_click);
}
}
}

RecyclerView的Item布局item_recycleview.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="vertical">
<!--
android:background="?android:attr/selectableItemBackground设置点击时水波纹效果 Android5.0以上有效果
-->
<TextView
android:id="@+id/btn_click"
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:gravity="center"
android:text="点击"/>
</LinearLayout>

实现RecyclerView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package com.example.michael.recyclerviewdemo;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private RecyclerViewAdapter mAdapter;
private StaggeredAdapter mStaggeredAdapter;
private List<String> mDatas;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
initView();
initData();
initEvent();
}
private void initView() {
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
}
private void initData() {
mDatas = new ArrayList<String>();
for(int i = 0;i < 20;i++) {
mDatas.add(String.valueOf(i));
}
setListView();
}
/**
* ListView效果
*/
private void setListView() {
//设置布局管理器
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); //垂直方向的list
// mRecyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false)); //水平方向的list
//增加Item分割线,必须在setAdapter方法之前设置
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
//设置增加和删除条目时的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = new RecyclerViewAdapter(this,mDatas);
mRecyclerView.setAdapter(mAdapter);
}
private void initEvent(){
//实现RecyclerView的点击事件
mAdapter.setOnItemClickListener(new RecyclerViewAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, final int position) {
Toast.makeText(MainActivity.this,"onItemClick: " +position,Toast.LENGTH_SHORT).show();
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("添加Item");
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mAdapter.addNextItem("add pre",position);
}
});
builder.show();
}
@Override
public void onItemLongClick(View view, final int position) {
Toast.makeText(MainActivity.this,"onItemLongClick " + position,Toast.LENGTH_SHORT).show();
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("删除Item");
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mAdapter.removeItem(position);
}
});
builder.show();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main,menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
Intent intent = new Intent(MainActivity.this,CardViewActivity.class);
startActivity(intent);
break;
}
return super.onOptionsItemSelected(item);
}
}

效果

ALT TEXT

RecyclerView实现GridView效果

RecyclerView实现GridView与实现ListView效果的不同之处有两点:

  • 布局管理器为StaggeredGridLayoutManager
  • Item间隔类为DividerGridItemDecoration

DividerGridItemDecoration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package com.example.michael.recyclerviewdemo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
/**
* GridView分割线
* Created by Michael on 2016/2/25.
*/
public class DividerGridItemDecoration extends RecyclerView.ItemDecoration {
//系统theme.xml中,可以自定义
private static final int[] ATTRS = new int[] { android.R.attr.listDivider };
private Drawable mDivider;
public DividerGridItemDecoration(Context context)
{
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
{
drawHorizontal(c, parent);
drawVertical(c, parent);
}
private int getSpanCount(RecyclerView parent)
{
// 列数
int spanCount = -1;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
spanCount = ((StaggeredGridLayoutManager) layoutManager)
.getSpanCount();
}
return spanCount;
}
public void drawHorizontal(Canvas c, RecyclerView parent)
{
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++)
{
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getLeft() - params.leftMargin;
final int right = child.getRight() + params.rightMargin
+ mDivider.getIntrinsicWidth();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawVertical(Canvas c, RecyclerView parent)
{
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++)
{
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getTop() - params.topMargin;
final int bottom = child.getBottom() + params.bottomMargin;
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private boolean isLastColum(RecyclerView parent, int pos, int spanCount,
int childCount)
{
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
{
return true;
}
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL)
{
if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
{
return true;
}
} else
{
childCount = childCount - childCount % spanCount;
if (pos >= childCount)// 如果是最后一列,则不需要绘制右边
return true;
}
}
return false;
}
private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
int childCount)
{
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
childCount = childCount - childCount % spanCount;
if (pos >= childCount)// 如果是最后一行,则不需要绘制底部
return true;
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
// StaggeredGridLayoutManager 且纵向滚动
if (orientation == StaggeredGridLayoutManager.VERTICAL)
{
childCount = childCount - childCount % spanCount;
// 如果是最后一行,则不需要绘制底部
if (pos >= childCount)
return true;
} else
// StaggeredGridLayoutManager 且横向滚动
{
// 如果是最后一行,则不需要绘制底部
if ((pos + 1) % spanCount == 0)
{
return true;
}
}
}
return false;
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition,
RecyclerView parent)
{
int spanCount = getSpanCount(parent);
int childCount = parent.getAdapter().getItemCount();
if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最后一行,则不需要绘制底部
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
} else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最后一列,则不需要绘制右边
{
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(),
mDivider.getIntrinsicHeight());
}
}
}

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* GridView效果
*/
private void setGridView() {
mRecyclerView.setLayoutManager(new GridLayoutManager(this,4));
mRecyclerView.addItemDecoration(new DividerGridItemDecoration(this));
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = new RecyclerViewAdapter(this,mDatas);
mRecyclerView.setAdapter(mAdapter);
}
或者
private void setGridView() {
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.VERTICAL));
mRecyclerView.addItemDecoration(new DividerGridItemDecoration(this));
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = new RecyclerViewAdapter(this,mDatas);
mRecyclerView.setAdapter(mAdapter);
}

注意:StaggeredGridLayoutManager构造的第二个参数传一个orientation,如果传入的是StaggeredGridLayoutManager.VERTICAL代表有多少列;那么传入的如果是StaggeredGridLayoutManager.HORIZONTAL就代表有多少行。

效果

ALT TEXT

RecyclerView实现瀑布流效果

RecyclerView实现瀑布流效果与实现ListView效果的不同之处有三点:

  • 布局管理器为StaggeredGridLayoutManager
  • Item间隔类为DividerGridItemDecoration
  • Adapter不同,实现瀑布流需要在adaper写一个随机的高度来控制每个item的高度就可以了,通常这个高度是由服务端返回的数据高度来控制的。

实现

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 瀑布流效果
*/
private void setWaterfallView() {
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL));
mRecyclerView.addItemDecoration(new DividerGridItemDecoration(this));
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mStaggeredAdapter = new StaggeredAdapter(this,mDatas);
mRecyclerView.setAdapter(mStaggeredAdapter);
}

注意:StaggeredGridLayoutManager构造的第二个参数传一个orientation,如果传入的是StaggeredGridLayoutManager.VERTICAL代表有多少列;那么传入的如果是StaggeredGridLayoutManager.HORIZONTAL就代表有多少行。

StaggeredAdapter

StaggeredAdapter写一个随机的高度来控制每个item的高度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package com.example.michael.recyclerviewdemo;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* 实现瀑布流的Adapter,增加一个高度列表,随机生成View的高度
* Created by Michael on 2016/2/25.
*/
public class StaggeredAdapter extends RecyclerView.Adapter<StaggeredAdapter.MyViewHolder> {
private Context mContext;
private List<String> mDatas;
private List<Integer> mHeights;
private OnItemClickListener mListener;
/**
* 点击事件回调接口
*/
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.mListener = listener;
}
public StaggeredAdapter(Context context, List<String> datas) {
this.mContext = context;
mDatas = datas;
mHeights = new ArrayList<Integer>();
//生成随机高度
for(int i = 0;i < mDatas.size();i++) {
mHeights.add((int) (100 + Math.random() * 300));
}
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_recyclerview, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
//设置Item的高度
ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
layoutParams.height = mHeights.get(position);
holder.itemView.setLayoutParams(layoutParams);
holder.mClickBtn.setText(mDatas.get(position));
if (mListener != null) {
holder.mClickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取实际的位置
int pos = holder.getLayoutPosition();
mListener.onItemClick(holder.mClickBtn,pos);
}
});
holder.mClickBtn.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();
mListener.onItemLongClick(holder.mClickBtn,pos);
return true; //屏蔽单击 false点击事件仍会响应
}
});
}
}
@Override
public int getItemCount() {
return mDatas.size();
}
/**
* 在当前Item之前添加Item
*/
public void addPreItem(String data,int position) {
mDatas.add(position,data);
mHeights.add( (int) (100 + Math.random() * 300));
notifyItemInserted(position);
}
/**
* 在当前Item之后添加Item
*/
public void addNextItem(String data,int position) {
mDatas.add(position+1,data);
mHeights.add((int) (100 + Math.random() * 300));
notifyItemInserted(position + 1);
}
/**
* 删除Item
* @param position
*/
public void removeItem(int position) {
mDatas.remove(position);
mHeights.remove(position);
notifyItemRemoved(position);
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView mClickBtn;
public MyViewHolder(View itemView) {
super(itemView);
mClickBtn = (TextView) itemView.findViewById(R.id.btn_click);
}
}
}

效果

ALT TEXT

修改分割线

DividerItemDecoration和DividerGridItemDecoration的实现类都是通过读取系统主题中的android.R.attr.listDivider来作为Item间的分割线,并且支持纵向和横向,获取到listDivider熟悉后,该属性的值是个Drawable,在getItemOffsets中,outRect去设置了绘制的范围,onDraw中实现了真正的绘制。

该分割线是系统默认的,你可以在theme.xml文件中找到该属性的使用情况。那么,使用系统的listDivide有什么好处呢?就是方便我们去随意的改变,该属性我们可以直接声明在:

1
2
3
4
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<item name="android:listDivider">@drawable/divider_bg</item>
</style>

然后自己写个Drawable即可,下面我们换一种分隔符

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:centerColor="#ff00ff00"
android:endColor="#ff0000ff"
android:startColor="#ffff0000"
android:type="linear" />
<size android:height="4dp"/>
</shape>

效果如下:

ALT TEXT

#参考资料

Android RecyclerView 使用完全解析 体验艺术般的控件
Android5.x RecyclerView 应用解析

刘涤生 wechat